using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using SpoonStun.Handler;
using SpoonStun.Message;

namespace SpoonStun
{
	public class StunServer
	{
		private Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
		private StunHandler _handler;
		private List<IPEndPoint> _serverList = new List<IPEndPoint>( );
		private List<IPEndPoint> _globalEndPoint = new List<IPEndPoint>();
		private StunCoreHandler _coreHandler;

		public StunServer(int port)
		{
			_socket.Bind(new IPEndPoint(IPAddress.Any, port));
			Init();
		}

		public StunServer()
		{
			_socket.Bind(new IPEndPoint(IPAddress.Any, 0));
			Init();
		}

		public List<IPEndPoint> GlobalEndPoint
		{
			get { return _globalEndPoint; }
			set { _globalEndPoint = value; }
		}

		public List<IPEndPoint> Servers
		{
			get { return _serverList; }
		}

		private void Init()
		{
			SocketState ss = new SocketState();
			_socket.BeginReceiveMessageFrom(ss.Buffer, 0, ss.Buffer.Length, SocketFlags.None, ref ss.Endpoint,
			                                   new AsyncCallback(ReceiveMessage), ss);

			_handler = new StunHandler(this);
			_coreHandler = new StunCoreHandler(_handler);
		}

		private void ReceiveMessage(IAsyncResult ar)
		{
			SocketState ss = (SocketState) ar.AsyncState;
			int count = _socket.EndReceiveMessageFrom(ar, ref ss.SocketFlags, ref ss.Endpoint, out ss.PacketInformation);
			byte[] buffer = new byte[count];
			Array.Copy(ss.Buffer, buffer, count);

			_handler.ReceiveUdpMessage((IPEndPoint)ss.Endpoint, buffer);

			_socket.BeginReceiveMessageFrom(ss.Buffer, 0, ss.Buffer.Length, SocketFlags.None, ref ss.Endpoint,
											   new AsyncCallback(ReceiveMessage), ss);
		}

		private class SocketState
		{
			public byte[] Buffer = new byte[65536];
			public EndPoint Endpoint = new IPEndPoint(IPAddress.Any, 0);
			public SocketFlags SocketFlags = SocketFlags.None;
			public IPPacketInformation PacketInformation;
		}

		public void Register()
		{
			RegisterMessage msg = new RegisterMessage();
			foreach (IPEndPoint client in Servers)
			{
				_handler.Send(client, msg);
			}
		}

		public void AddServer(IPEndPoint endpoint)
		{
			Servers.Add(endpoint);
		}

		public void SendTo(byte[] bytes, IPEndPoint client)
		{
			_socket.SendTo(bytes, client);
		}

		internal void PeerWith(List<IPEndPoint> IPEndPoint)
		{
			_coreHandler.PeerWith(IPEndPoint);
		}

		internal void AddGlobalEndPoint(IPEndPoint endPoint)
		{
			if(!GlobalEndPoint.Contains(endPoint))
			{
				GlobalEndPoint.Add(endPoint);
			}
		}
	}
}